
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include "xm_app_config.h"
#include "xcutil.h"
#include "unistd.h"


#include <string>
#include <iostream>

enum EXTRA_OPTS {
    EXTRA_OPT_BASE=256,

    MEDIA_IP2_OPT,
    MIN_PORT_OPT,
    MAX_PORT_OPT,
    HTTP_IP_OPT,
    HTTP_PORT_OPT,
    RECORD_PATH_OPT,
    IP1_SRC_OPT,
    IP1_DST_OPT,
    IP2_SRC_OPT,
    IP2_DST_OPT,
    ECHO_OPT,
    FBPASS_OPT,
    DEBUG_OPT,
    DUMP_OPT,
    MAX_VIDEO_BITRATE_OPT,
    MIN_VIDEO_BITRATE_OPT,
    TRANSLATE_FEC_OPT,
    DISABLE_H264_OPT,
    ENABLE_FEC_OPT,
    COMPENSATE_RTCP_OPT,
    EXPERIMENT_ESMB_OPT,
    LOST_0_OPT,
    ENABLE_BWE_OPT,
    ENABLE_MIXER_DEBUG_OPT,
    FORCE_MIXER_RECORDING_OPT,
    MIXER_ACTIVATE_LEVEL_OPT,
    MIXER_OPUS_LSB_OPT,
    MIXER_TALKER_NUMBER_OPT,
    HTTP_EXTERNAL_IP_OPT,
    HTTP_EXTERNAL_PORT_OPT,
    IPM_IP_OPT,
    PROXY_ADDR_OPT,
    BATCH_SEND_OPT,
    EXTRA_OPT_MAX,
    MACHINE_ID,
    LOG_IF_LOST_RATE_GE,
    LOG_IF_RTT_GE,
    NICE_MUX,
    CRASH_LOG_DIR_OPT,
};

static const struct option long_options[] = {
    { "help", no_argument, NULL, 'h' },
    { "log-config", required_argument, NULL, 'c' },
    { "media-ip", required_argument, NULL, 'm' },
    { "media-ip2", required_argument, NULL, MEDIA_IP2_OPT },
    { "min-port", required_argument, NULL, MIN_PORT_OPT },
    { "max-port", required_argument, NULL, MAX_PORT_OPT },
    { "http-ip", required_argument, NULL, HTTP_IP_OPT },
    { "http-port", required_argument, NULL, HTTP_PORT_OPT },
    { "record-path", required_argument, NULL, RECORD_PATH_OPT },
    { "ip1-src", required_argument, NULL, IP1_SRC_OPT },
    { "ip1-dst", required_argument, NULL, IP1_DST_OPT },
    { "ip2-src", required_argument, NULL, IP2_SRC_OPT },
    { "ip2-dst", required_argument, NULL, IP2_DST_OPT },
    { "echo", required_argument, NULL, ECHO_OPT },
    { "fbpass", no_argument, NULL, FBPASS_OPT },
    { "debug", required_argument, NULL, DEBUG_OPT },
    { "dump", required_argument, NULL, DUMP_OPT },
    { "max-video-bitrate", required_argument, NULL, MAX_VIDEO_BITRATE_OPT },
    { "min-video-bitrate", required_argument, NULL, MIN_VIDEO_BITRATE_OPT },
    { "translate-fec", no_argument, NULL, TRANSLATE_FEC_OPT},
    { "disable-h264", no_argument, NULL, DISABLE_H264_OPT},
    { "enable-fec", no_argument, NULL, ENABLE_FEC_OPT},
    { "compensate-rtcp", no_argument, NULL, COMPENSATE_RTCP_OPT},
    { "experiment-esmb", no_argument, NULL, EXPERIMENT_ESMB_OPT},
    { "lost-0", no_argument, NULL, LOST_0_OPT},
    { "enable-bwe", no_argument, NULL, ENABLE_BWE_OPT},
    { "enable-mixer-debug", no_argument, NULL, ENABLE_MIXER_DEBUG_OPT},
    { "force-mixer-recording", no_argument, NULL, FORCE_MIXER_RECORDING_OPT},
    { "mixer-activate-level", required_argument, NULL, MIXER_ACTIVATE_LEVEL_OPT},
    { "mixer-talker-number",  required_argument, NULL, MIXER_TALKER_NUMBER_OPT},
    { "http-external-ip", required_argument, NULL, HTTP_EXTERNAL_IP_OPT},
    { "http-external-port", required_argument, NULL, HTTP_EXTERNAL_PORT_OPT},
    { "ipm-ip", required_argument, NULL, IPM_IP_OPT},
    { "proxy-addr", required_argument, NULL, PROXY_ADDR_OPT},
    { "batch-send", required_argument, NULL, BATCH_SEND_OPT},
    { "machine-id", required_argument, NULL, MACHINE_ID},
    { "log-if-lost-rate-ge", required_argument, NULL, LOG_IF_LOST_RATE_GE},
    { "log-if-rtt-ge", required_argument, NULL, LOG_IF_RTT_GE},
    { "nice-mux", no_argument, NULL, NICE_MUX},
    { "crash-log-dir", required_argument, NULL, CRASH_LOG_DIR_OPT},
    { NULL, no_argument, NULL, 0 }
};


static const char * short_options = "hc:m:";


static
void print_usage(int argc, char **argv){
//    fprintf(stderr, "Usage: %s [-c log_config_file] [-m media_ip] [--min-port port] [--max-port port]\n", argv[0]);
    fprintf(stderr, "Usage: %s [options]\n", argv[0]);
    fprintf(stderr, "Options:\n");
    fprintf(stderr, "  -h,--help            \n");
    fprintf(stderr, "               print this message\n");
    fprintf(stderr, "  -c,--log-config      [path/]<log-config-file>\n");
    fprintf(stderr, "               where to locate the log4cplus properties file\n");
    fprintf(stderr, "  -m,--media-ip        <local-ip>\n");
    fprintf(stderr, "               media ip which will be announced in ICE candidate\n");
    fprintf(stderr, "  --media-ip2          <local-ip>\n");
    fprintf(stderr, "               media ip2 which will be replace --media-ip in ICE candidate\n");
    fprintf(stderr, "  --min-port           <port>\n");
    fprintf(stderr, "               media port range will be announced in ICE candidate\n");
    fprintf(stderr, "  --max-port           <port>\n");
    fprintf(stderr, "               media port range will be announced in ICE candidate\n");
    fprintf(stderr, "  --http-ip            <local-ip>\n");
    fprintf(stderr, "               ip and port the http api will listen\n");
    fprintf(stderr, "  --http-port          <port>\n");
    fprintf(stderr, "               ip and port the http api will listen, if not specified will use any port available\n");
    fprintf(stderr, "  --http-external-ip   <external-ip>\n");
    fprintf(stderr, "  --http-external-port <external-port>\n");
    fprintf(stderr, "               NAT mapped ip and port\n");
    fprintf(stderr, "  --record-path        <path>\n");
    fprintf(stderr, "               where to put tlv and job files\n");
    fprintf(stderr, "  --ip1-src            <ip>\n");
    fprintf(stderr, "  --ip1-dst            <ip>\n");
    fprintf(stderr, "  --ip2-src            <ip>\n");
    fprintf(stderr, "  --ip2-dst            <ip>\n");
    fprintf(stderr, "  --echo               <1>\n");
    fprintf(stderr, "  --fbpass \n");
    fprintf(stderr, "               let rtcp feebback pass through\n");
    fprintf(stderr, "  --debug              connector|loopback|loopback-audio|mixer \n");
    fprintf(stderr, "               enable one of those debugging mode\n");
    fprintf(stderr, "  --dump               rtp|rtcp \n");
    fprintf(stderr, "               dump rtp or rtcp traffic, use `--dump rtp --dump rtcp` to dump both\n");
    fprintf(stderr, "  --max-video-bitrate     <kbps>\n");
    fprintf(stderr, "  --min-video-bitrate     <kbps>\n");
    fprintf(stderr, "               instruct chrome to set max/min video bitrate in kbps\n");
    fprintf(stderr, "  --translate-fec      \n");
    fprintf(stderr, "               terminate the fec stream\n");
    fprintf(stderr, "  --disable-h264\n");
    fprintf(stderr, "               disable h264\n");
    fprintf(stderr, "  --enable-fec\n");
    fprintf(stderr, "               enable fec\n");
    fprintf(stderr, "  --compensate-rtt\n");
    fprintf(stderr, "               add subscriber's rtt into publisher\n");
    fprintf(stderr, "  --experiment-esmb\n");
    fprintf(stderr, "               this is experimental\n");
    fprintf(stderr, "               will send esmb rtcp feedback\n");
    fprintf(stderr, "  --lost-0\n");
    fprintf(stderr, "               send lost = 0 in rtcp rr \n");
    fprintf(stderr, "  --enable-bwe\n");
    fprintf(stderr, "               enable bwe\n");
    fprintf(stderr, "  --enable-mixer-debug\n");
    fprintf(stderr, "               enable mixer debug\n");
    fprintf(stderr, "  --force-mixer-recording\n");
    fprintf(stderr, "               force mixer to record audio files\n");
    fprintf(stderr, "  --mixer-activate-level\n");
    fprintf(stderr, "               minimum audio level to be considered as a talker, default -127\n");
    fprintf(stderr, "  --mixer-opus-lsb\n");
    fprintf(stderr, "               opus lsb setting, 8-16, default 16\n");
    fprintf(stderr, "  --mixer-talker-number <n>\n");
    fprintf(stderr, "               how many talkers in mixer conference, min 1, max 5\n");
    fprintf(stderr, "  --ipm-ip <ip>\n");
    fprintf(stderr, "               inter-process media ip, default to 127.0.0.1\n");
    fprintf(stderr, "  --proxy-addr <proxy-ip>:<proxy-backend-port>");
    fprintf(stderr, "  --batch-send <packets>:<duration>\n");
    fprintf(stderr, "               batch send rtp packet, queue 5 packets or 20ms before send\n");
    fprintf(stderr, "  --machine-id           [0, 8192)\n");
    fprintf(stderr, "               code xswitch instance, use to gen unique ssrc. if not config, will user ip last & 8191\n");
    fprintf(stderr, "  --nice-mux\n");
    fprintf(stderr, "               support singal port, mux socket \n");
    fprintf(stderr, "  --crash-log-dir\n");
    fprintf(stderr, "               [path/]crash info output dir.\n");
}


app_config * app_config_get(){
    static app_config g_config;
    return &g_config;
}


int app_config_parse (int argc, char **argv){
//    print_usage(argc, argv);
    
    app_config * config = app_config_get();
    memset(config, 0, sizeof(app_config));
    config->log_config_file_name = "";
    config->media_ip = "";
    config->media_ip2 = nullptr;
    config->http_ip = nullptr;
    config->http_port = 0;
    config->http_external_ip = nullptr;
    config->http_external_port = 0;

    sprintf(config->tlv_path, "/tmp/rtc-xswitch/tlv");
    sprintf(config->job_path, "/tmp/rtc-xswitch/job");
    config->ip1_src = "";
    config->ip1_dst = "";
    config->ip2_src = "";
    config->ip2_dst = "";
    config->feedback_passthrough = false;
    config->is_dumping_rtp = false;
    config->is_dumping_rtcp = false;
    config->max_video_bitrate_kbps = 0;
    config->min_video_bitrate_kbps = 0; /* 0 means not using this feature */
    config->is_translating_fec = false;
    config->disable_h264 = false;
    config->enable_fec = false;
    config->compensate_rtcp = false;
    config->experiment_esmb = false;
    config->lost_0 = false;
    config->enable_bwe = false;
    config->enable_mixer_debug = false;
    config->force_mixer_recording = false;
    config->mixer_activate_level = -127;
    config->mixer_opus_lsb = 16;
    config->mixer_talker_number = 2;
    config->ipm_ip = "127.0.0.1";
    config->proxy_addr = nullptr;
    config->batch_send = false;
    config->batch_send_packets = 5;
    config->batch_send_duration = 20;
    config->machineId = -1;

    config->log_if_lost_rate_ge = 0;
    config->log_if_rtt_ge = 0;
    config->nice_mux = 0;
    config->crash_log_dir = "/tmp";
    // parse command line
    // see https://www.gnu.org/software/libc/manual/html_node/Getopt.html#Getopt
    // see https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Option-Example.html
    
    int c;
    opterr = 0;
    
    int option_index = 0;
    
    //while ((c = getopt (argc, argv, short_options)) != -1){
    while ((c = getopt_long (argc, argv, short_options, long_options, &option_index)) != -1){
        switch (c){
            case 'h':
                print_usage(argc, argv);
                return 1;
                break;
            case 'c':
                config->log_config_file_name = optarg;
                break;
            case 'm':
                config->media_ip = optarg;
                break;
            case MEDIA_IP2_OPT:
                config->media_ip2 = optarg;
                break;
            case MIN_PORT_OPT:
                config->min_port = atoi(optarg);
                break;
            case MAX_PORT_OPT:
                config->max_port = atoi(optarg);
                break;
            case HTTP_IP_OPT:
                config->http_ip = optarg;
                break;
            case HTTP_PORT_OPT:
                config->http_port = atoi(optarg);
                break;
            case HTTP_EXTERNAL_IP_OPT:
                config->http_external_ip = optarg;
                break;
            case HTTP_EXTERNAL_PORT_OPT:
                config->http_external_port = atoi(optarg);
                break;
            case RECORD_PATH_OPT:
                sprintf(config->tlv_path, "%s/tlv", optarg);
                mkdir_tree(config->tlv_path);
                sprintf(config->job_path, "%s/job", optarg);
                mkdir_tree(config->job_path);
                break;
            case IP1_SRC_OPT:
                config->ip1_src = optarg;
                break;
            case IP1_DST_OPT:
                config->ip1_dst = optarg;
                break;
            case IP2_SRC_OPT:
                config->ip2_src = optarg;
                break;
            case IP2_DST_OPT:
                config->ip2_dst = optarg;
                break;
            case ECHO_OPT:
                config->echo = atoi(optarg);
                break;
            case FBPASS_OPT:
                config->feedback_passthrough = true;
                break;
            case DEBUG_OPT:
                config->debug_mode_string = optarg;
                break;
            case DUMP_OPT:
                if (strcmp(optarg, "rtp") == 0) {
                    config->is_dumping_rtp = true;
                } else if (strcmp(optarg, "rtcp") == 0) {
                    config->is_dumping_rtcp = true;
                }
                break;
            case MAX_VIDEO_BITRATE_OPT:
                config->max_video_bitrate_kbps = atoi(optarg);
                break;
            case MIN_VIDEO_BITRATE_OPT:
                config->min_video_bitrate_kbps = atoi(optarg);
                break;
            case TRANSLATE_FEC_OPT:
                config->is_translating_fec = true;
                break;
            case DISABLE_H264_OPT:
                config->disable_h264 = true;
                break;
            case ENABLE_FEC_OPT:
                config->enable_fec = true;
                break;
            case COMPENSATE_RTCP_OPT:
                config->compensate_rtcp = true;
                break;
            case EXPERIMENT_ESMB_OPT:
                config->experiment_esmb = true;
                break;
            case LOST_0_OPT:
                config->lost_0 = true;
                break;
            case ENABLE_BWE_OPT:
                config->enable_bwe = true;
                break;
            case ENABLE_MIXER_DEBUG_OPT:
                config->enable_mixer_debug = true;
                break;
            case FORCE_MIXER_RECORDING_OPT:
                config->force_mixer_recording = true;
                break;
            case MIXER_ACTIVATE_LEVEL_OPT:
                config->mixer_activate_level = -atoi(optarg);
                if (config->mixer_activate_level < -127) {
                    config->mixer_activate_level = -127;
                } else if (config->mixer_activate_level > 0) {
                    config->mixer_activate_level = 0;
                }
                break;
            case MIXER_OPUS_LSB_OPT:
                config->mixer_opus_lsb = atoi(optarg);
                if (config->mixer_opus_lsb > 16) {
                    config->mixer_opus_lsb = 16;
                } else if (config->mixer_opus_lsb < 8) {
                    config->mixer_opus_lsb = 8;
                }
                break;
            case MIXER_TALKER_NUMBER_OPT:
                config->mixer_talker_number = atoi(optarg);
                if (config->mixer_talker_number < 1) {
                    config->mixer_talker_number = 1;
                } else if (config->mixer_talker_number > 5) {
                    config->mixer_talker_number = 5;
                }
                break;
            case IPM_IP_OPT:
                config->ipm_ip = optarg;
                break;
            case PROXY_ADDR_OPT:
                config->proxy_addr = optarg;
                break;
            case BATCH_SEND_OPT: {
                char* opt_end = optarg + strlen(optarg);
                char* packet_str = optarg;
                int packet = atoi(packet_str);
                if (packet <= 0 || packet > 1000) {
                    fprintf(stderr, "invalid batch send packet option\n");
                    continue;
                }
                char *duration_str = strchr(optarg, ':');
                if (duration_str == nullptr) {
                    continue;
                }

                *duration_str = '\0';
                duration_str++;

                if (duration_str > opt_end) {
                    continue;
                }

                int duration = atoi(duration_str);
                if (duration <= 0 || duration > 1000) {
                    fprintf(stderr, "invalid batch send duration option\n");
                    continue;
                }
                
                config->batch_send = true;
                config->batch_send_packets = packet;
                config->batch_send_duration = duration;
            } break;
            case MACHINE_ID:
                config->machineId = atoi(optarg);
                break;
            case LOG_IF_LOST_RATE_GE:
                config->log_if_lost_rate_ge = atoi(optarg);
                break;
            case LOG_IF_RTT_GE:
                config->log_if_rtt_ge = atoi(optarg);
                break;
            case NICE_MUX:
                config->nice_mux = 1;
                fprintf (stderr, "nice_mux: %d \n", config->nice_mux);
                break;
            case CRASH_LOG_DIR_OPT:
                if(access(optarg, F_OK)) {
                    fprintf (stderr, "Option -%c requires that the dir must exist.\n", optopt);
                    print_usage(argc, argv);
                    return -1;
                }
                config->crash_log_dir = optarg;
                break;
            case '?':
                if (optopt == 'c' || optopt=='m'){
                    fprintf (stderr, "Option -%c requires an argument.\n", optopt);
                } else if (isprint (optopt)){
                    fprintf (stderr, "Unknown option `-%c'.\n", optopt);
                }else{
                    fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
                }
                print_usage(argc, argv);
                return 1;
            default:
                fprintf (stderr, "Unknown option `-%c'.\n", optopt);
                print_usage(argc, argv);
                return 1;
        }
    }
    
    if(config->min_port > config->max_port
       || config->min_port < 0
       || config->min_port > 65535
       || config->max_port < 0
       || config->max_port > 65535){
        fprintf (stderr, "error port range, min-port=[%d], max-port=[%d]\n", config->min_port, config->max_port);
        return 1;
    }

    if (config->http_ip == nullptr) {
        fprintf(stderr, "option '--http-ip' not set\n");
        return 1;
    }

    if (config->proxy_addr == nullptr) {
        fprintf(stderr, "option '--proxy-addr' not set\n");
        return 1;
    }

    if (config->machineId < 0){
        std::string ip(config->http_ip);
        std::size_t index = ip.rfind(".");
        std::size_t index2 = ip.rfind(".", index - 1);

        int first = std::stoi(ip.substr(index + 1));
        int second = std::stoi(ip.substr(index2 + 1, index - index2 -1));
        config->machineId = second * 256 + first;
    }
    fprintf (stderr, "Ip:%s -> matchineId: %d \n", config->http_ip, config->machineId);

    
//    dump_config(config);
    
    return 0;
}
